/**
    \class Rails
    \brief Obiekt torów.

    Określony jest przez źródło i cel (czyli skąd i dokąd jadą dane tory) oraz podłoże źródła i celu (czyli czy tory są fragmentem jezdni, czy nie).

    Przy pierwszym rysowaniu obliczany jest rodzaj połączenia między torami źródłowymi a docelowymi. Wynik obliczeń przechowywany jest w polach składowych i udostępniany, ponieważ przydaje się to do animacji tramwajów.
  */

#include "rails.h"
#include <QPainter>
#include <QtGlobal>
#include <QPointF>
#include <QLineF>
#include "qroundabout.h"
#include <QDebug>
#include <cmath>

#define PI 3.14159265358979323846264338327950288419716939937510

Rails::Rails(int from, int to, RailBed fromRailBed, RailBed toRailBed, qreal width, QRoundabout *r)
    :_from(from), _to(to), _width(width), _fromRailBed(fromRailBed), _toRailBed(toRailBed), _qr(r),
     _pointsComputed(false), _srcLine(), _dstLine(),_intersectionPoint()
{
    if (to < 0)
    {
        to = 0;
        qWarning("Bad parameters for rails constructor!");
    }
    if (from < 0)
    {
        from = 0;
        qWarning("Bad parameters for rails constructor!");
    }

    _intersectionPoint = new QPointF();  // po prostu żeby był zainicjalizowany
}


/**
  \brief Funkcja oblicza kluczowe punkty (i inne wartości) niezbędne do nayrsowania torów.

  Tutaj zapada decyzja o użyciu (lub nie) eliptycznego połączenia. Odpowiednie dane są zapisywane w polach klasowych, a niektóre z nich są potem udostępniane tramwajom, aby zapewnić wskazówki na temat ich animacji.
  computePoints jest wywoływane na początku Rails::draw(), jeżeli tylko _pointsComputed wynosi false. Dzięki temu teoretycznie możliwe jest edytowanie graficznych właściwości torów "na żywo".
  */
void Rails::computePoints(qreal fromFi, qreal toFi, qreal dist)
{
    QPointF fromDist(-1000.0 * sin(fromFi*PI/180.0), 1000.0 * cos(fromFi*PI/180.0));
    QPointF toDist(-1000.0 * sin(toFi*PI/180.0), 1000.0 * cos(toFi*PI/180.0));

    QPointF fromPoint(-dist * sin(fromFi*PI/180.0), dist * cos(fromFi*PI/180.0));
    QPointF toPoint(-dist * sin(toFi*PI/180.0), dist * cos(toFi*PI/180.0));

/*
    Należy się dodatkowo przesunąć w jakąś stronę, jeżeli jesteśmy na jezdni, bo chcemy zawsze być na skrajnym pasie!
    są trzy możliwości:
    i) ja jestem sklejony
    ii) jeśli nie, poprzedni jest sklejony
    iii) jeśli nie, jestem sam.
*/

    qreal fromReposition = 0, toReposition = 0;
    if (_fromRailBed == onroad)
        fromReposition = repositionFromPoints(&fromPoint, &fromDist);
    if (_toRailBed == onroad)
        toReposition = repositionToPoints(&toPoint, &toDist);

    _srcLine.setPoints(fromPoint, fromDist);
    _dstLine.setPoints(toPoint, toDist);

    /*
        teraz muszę z tych punktów uzyskać _fromDistLeft, _toDistLeft, _fromPointLeft, _toPointLeft i analogiczne cztery dla right
        KONWENCJA: Lewa strona jest lewa z perspektywy źródła (from) !!! */
    _fromDistLeft = new QPointF(fromDist);
    _toDistLeft = new QPointF(toDist);
    _fromPointLeft = new QPointF(fromPoint);
    _toPointLeft = new QPointF(toPoint);
    _fromDistRight = new QPointF(fromDist);
    _toDistRight = new QPointF(toDist);
    _fromPointRight = new QPointF(fromPoint);
    _toPointRight = new QPointF(toPoint);

    qreal d = _width/2;
    // ppniższe movePointy "tworzą" mi lewą i prawą szynę
    movePoints(_fromDistLeft, _fromPointLeft, -d);
    movePoints(_fromDistRight, _fromPointRight, +d);

    movePoints(_toDistLeft, _toPointLeft, +d);
    movePoints(_toDistRight, _toPointRight, -d);

    _srcLine.normalVector().intersect( _dstLine.normalVector(), _intersectionPoint );

    if ((normalizeDegAngle(fromFi - toFi) >= 45) && (normalizeDegAngle(fromFi - toFi) <= 225))
    {
        _spanAngle = _srcLine.normalVector().angleTo(_dstLine.normalVector()) - 180;
//        qDebug() << "<45; 225>";
    }
    else
    {
        _spanAngle = abs(_dstLine.normalVector().angleTo(_srcLine.normalVector()) - 180);
//        qDebug() << "(225; 45)";
    }

    if (normalizeDegAngle(fromFi - toFi) < 180)
        _rotFactor = 180;
    else
        _rotFactor = 0;

/*
    mam tylko promień b mojej elipsy. Ze wzorku na elipsę, wyciągnąłem:
    b, to mój znany promień, czyli
    druga półoś a ma taką samą długość + jej przesunięcie;
*/

    if (!_rotFactor)
        toReposition = -toReposition;
    qreal a, b;  // dwie półosie elipsy
    b = QLineF(*_fromPointLeft, *_intersectionPoint).length();
    a = b + toReposition + fromReposition;
    _ellipseLeftRectF = new QRectF( -b, -a, 2*b, 2*a);

    b = QLineF(*_fromPointRight, *_intersectionPoint).length();
    a = b + toReposition + fromReposition;
    _ellipseRightRectF = new QRectF( -b, -a, 2*b, 2*a);


    /* Kiedy wyliczyłem elipsę która jest do chrzanu? Albo jak a lub b są w kosmos duże

        przesunięcie from i to są sobie różne, a kąt nie jest podzielny przez 90 */

//    qDebug() << "tory from" << _from << "to" << _to << _srcLine.angle();

    if (((a < 1000000) && (b < 1000000)))
        _ellipseUsed = true;
    else
        _ellipseUsed = false;

     qDebug() << toReposition << fromReposition << int(normalizeDegAngle(_spanAngle)) << int(normalizeDegAngle(_spanAngle)) % 90;
     if ((toReposition != -fromReposition) && (int(normalizeDegAngle(_spanAngle) ) % 90 != 0))
    {
//        qDebug() << QObject::tr("przesunięcie from i to są sobie różne, a kąt nie jest podzielny przez 90") << toReposition << fromReposition;
        _ellipseUsed = false;
    }

    _pointsComputed = true;
}

/**
  Rysowanie torów to tak naprawdę trzy elementy. Tory ze źródła od dist aż do skraju placu ronda, analogiczne z celu oraz
  połączenie między nimi, które jest wycinkiem elipsy lub zwykłymi prostymi odcinkami.
  W pewnych okolicznościach narysowanie łuku jest niemożliwe. Powodem tego jest fakt, że tory, które mają być na jezdni
  są przesuwane prostopadle do ich kierunku, aby zawsze były na skrajnie zewnętrznym pasie.
  Czasami łuk nie jest rysowany, mimo takiej teoretyczne możliwości. Matematyka zawiodła nas, gdy trzeba narysować elipsę o różnych promieniach pod kątem niebędącym wielokrotnością dziewięćdziesięciu stopni.

  Parametry są nam przekazywane przez QRoundabout, ponieważ to tam analizowane jest położenie torów względem reszty ronda.

  \param painter Obiekt, służący do rysowania
  \param fromFi kąt odchylenia torów ze źródła
  \param toFi kąt odchylenia torów do celu
  \param dist odległość, na jaką rysowane są proste tory.
  \see QRoundabout::drawRails()
  */
void Rails::draw(QPainter *painter, qreal fromFi, qreal toFi, qreal dist)
{
    if (_pointsComputed == false)
        computePoints(fromFi, toFi, dist);

    QPen pen;
    pen.setStyle(Qt::SolidLine);
    pen.setColor(QColor(129, 67, 19));
    pen.setWidth(3);
    painter->setPen(pen);

    // rysuję proste odcinki torów
    painter->drawLine(*_fromDistLeft, *_fromPointLeft);
    painter->drawLine(*_fromDistRight, *_fromPointRight);

    painter->drawLine(*_toDistLeft, *_toPointLeft);
    painter->drawLine(*_toDistRight, *_toPointRight);

    if (_ellipseUsed)
    {
        painter->translate(*_intersectionPoint);
        painter->rotate(fromFi + _rotFactor);
        painter->drawArc(*_ellipseLeftRectF, 0, 16.0 * _spanAngle);
        painter->drawArc(*_ellipseRightRectF, 0, 16.0 * _spanAngle);
        painter->rotate(-fromFi - _rotFactor);
        painter->translate(-*_intersectionPoint);
    }
    else
    {
        painter->drawLine(*_fromPointLeft, *_toPointLeft);
        painter->drawLine(*_fromPointRight, *_toPointRight);
    }
}

/**
    \brief Funkcja użytku wewnętrznego.

    Jako argumentów oczekuje punktów charakteryzujących tory ze źródła. Przesuwa te punkty tak, aby tory wyznaczone punktami leciały po wewnętrznej (lewej) stronie pasa.
  */
qreal Rails::repositionFromPoints(QPointF *fromPoint, QPointF *fromDist)
{
    qreal d = 0;
    int prev = minusModulo(_from, 1, _qr->getBranches()->size());
    int next = plusModulo(_from, 1, _qr->getBranches()->size());
    if (_fromRailBed == onroad)
    {
        if (_qr->getDependences()->at(_from) == glued)
        {
            // przesuwam się na skrajnie prawy pas, czyli o ((szerokość obu jezdni)/2 - 0.5 pasa)
            d = _qr->getBranches()->at(_from)->getWidth() + _qr->getBranches()->at(next)->getWidth();
            d /= 2;
            d -= _qr->getLaneWidth()/2;
            // przesuwam się o (liczbę pasów-1) z mojej gałęzi w lewo
            d -= (_qr->getBranches()->at(_from)->getWidth() - _qr->getLaneWidth());
        }
        else if (_qr->getDependences()->at(prev) == glued)
        {
            // ZONK - nie powinny być dwa wjazdy obok siebie sklejone
        }
        else
        { // jestem sam.
            // przesuwam się na skrajnie prawy pas, czyli o ((szerokość jezdni)/2 - 0.5 pasa)
            d = _qr->getBranches()->at(_from)->getWidth();
            d /= 2;
            d -= _qr->getLaneWidth()/2;
            // przesuwam się o (liczbę pasów-1) z mojej gałęzi w lewo
            d -= (_qr->getBranches()->at(_from)->getWidth() - _qr->getLaneWidth());
        }
    }
    movePoints(fromDist, fromPoint, d); // powinienem być na środku skrajnie wewnętrznego pasa
    return d;
}


/**
    \brief Funkcja użytku wewnętrznego.

    Jako argumentów oczekuje punktów charakteryzujących tory do celu. Przesuwa te punkty tak, aby tory wyznaczone punktami leciały po wewnętrznej (lewej) stronie pasa.
  */
qreal Rails::repositionToPoints(QPointF *toPoint, QPointF *toDist)
{
    qreal d = 0;
    int prev = minusModulo(_to, 1, _qr->getBranches()->size());
    int next = plusModulo(_to, 1, _qr->getBranches()->size());
    if (_toRailBed == onroad)
    {
        if (_qr->getDependences()->at(_to) == glued)
        {
            // co? zjazd nie może być sklejony!
        }
        else if (_qr->getDependences()->at(prev) == glued)
        {
            // przesuwam się na skrajnie lewy pas, czyli o ((szerokość obu jezdni)/2 - 0.5 pasa)
            d = _qr->getBranches()->at(_to)->getWidth() + _qr->getBranches()->at(next)->getWidth();
            d /= 2;
            d -= _qr->getLaneWidth()/2;
            d *= -1;
            // przesuwam się o (liczbę pasów-1) z mojej gałęzi w prawo
            d += (_qr->getBranches()->at(_to)->getWidth() - _qr->getLaneWidth());
        }
        else
        { // jestem sam.
            // przesuwam się na skrajnie lewy pas, czyli o ((szerokość jezdni)/2 - 0.5 pasa)
            d = _qr->getBranches()->at(_to)->getWidth();
            d /= 2;
            d -= _qr->getLaneWidth()/2;
            d *= -1;
            // przesuwam się o (liczbę pasów-1) z mojej gałęzi w prawo
            d += (_qr->getBranches()->at(_to)->getWidth() - _qr->getLaneWidth());
        }
    }
    movePoints(toDist, toPoint, d); // powinienem być na środku skrajnie wewnętrznego pasa
    return d;
}

int Rails::getFrom() const
{    return _from;  }
int Rails::getTo() const
{    return _to;  }
int Rails::getFromRailBed() const
{    return _fromRailBed;  }
int Rails::getToRailBed() const
{    return _toRailBed;  }
QLineF Rails::getSrcLine() const
{   return _srcLine;    }
QLineF Rails::getDstLine() const
{   return _dstLine;    }
QPointF Rails::getIntersectionPoint() const
{   return (*_intersectionPoint);   }
bool Rails::isEllipseUsed() const
{   return _ellipseUsed;    }
qreal Rails::getRotFactor() const
{   return _rotFactor;  }

void Rails::setFromRailBed(RailBed a)
{
    _fromRailBed = a;
    _pointsComputed = false;
}
void Rails::setToRailBed(RailBed b)
{
    _toRailBed = b;
    _pointsComputed = false;
}
